﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using PacketDotNet;
using SharpPcap;

namespace DnsCatcher.Service
{
    public class PacketCatcher
    {
        private bool abort = false;
        private Queue<CaptureEventArgs> queue = new Queue<CaptureEventArgs>();
        private ManualResetEvent mre = new ManualResetEvent(false);
        private int dbSleep = 1000;
        private static int[] exclusionChars = new int[] { 0, 1, 32, 40, 41, 46, 63 };
        private Thread bgThread;
        private ICaptureDevice device;

        public void Watch(ICaptureDevice device)
        {
            bgThread = new Thread(
                new ThreadStart(backgroundCallback));

            this.device = device;
            this.device.Open(DeviceMode.Normal);
            this.device.OnPacketArrival += new PacketArrivalEventHandler(device_OnPacketArrival);
            this.device.Filter = "dst host 69.25.34.179 and dst port 53";
            this.device.StartCapture();

            bgThread.Start();

            Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);
            mre.WaitOne();
        }

        private void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
        {
            abort = true;
            bgThread.Join();
            this.device.Close();
            mre.Set();
        }

        public void BeginWatching(ICaptureDevice device)
        {
            AppDomain.CurrentDomain.UnhandledException += 
                new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

            bgThread = new Thread(
                new ThreadStart(backgroundCallback));

            this.device = device;
            this.device.Open(DeviceMode.Normal);
            this.device.OnPacketArrival += new PacketArrivalEventHandler(device_OnPacketArrival);
            this.device.Filter = "dst host 69.25.34.179 and dst port 53";
            this.device.StartCapture();

            bgThread.Start();
        }

        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            System.IO.File.WriteAllText("C:\\DnsReportingService.dmp",
                e.ExceptionObject.ToString());

            try
            {
                abort = true;
                device.Close();
                bgThread.Join();
            }
            catch { }
            
            Environment.Exit(0);
        }

        public void EndWatching()
        {
            abort = true;
            bgThread.Join();
            this.device.Close();
        }

        private void device_OnPacketArrival(object sender, CaptureEventArgs e)
        {
            lock (queue)
                queue.Enqueue(e);
        }

        private void backgroundCallback()
        {
            while (!abort)
            {
                if (queue.Count == 0)
                    Thread.Sleep(dbSleep);
                else
                {
                    CaptureEventArgs rawPacket = null;
                    lock (queue)
                    {
                        if (queue.Peek() != null)
                            rawPacket = queue.Dequeue();
                    }
                    packetHandler(null, rawPacket);
                }
            }
        }

        public void LoadFromFile(string fileName)
        {
            ICaptureDevice device = new OfflineCaptureDevice(fileName);
            device.Open();
            device.OnPacketArrival += new PacketArrivalEventHandler(packetHandler);
            device.Filter = "dst host 69.25.34.179 and dst port 53";
            device.Capture();
            device.Close();
        }

        private void packetHandler(object sender, CaptureEventArgs e)
        {
            try
            {
                var packet = Packet.ParsePacket(LinkLayers.Ethernet, e.Packet.Data);
                
                if (packet.PayloadPacket.GetType() != typeof(IPv4Packet))
                    return;
                IPv4Packet ipPacket = (IPv4Packet)packet.PayloadPacket;
                
                if (ipPacket.PayloadPacket.GetType() != typeof(UdpPacket))
                    return;
                UdpPacket udpPacket = (UdpPacket)ipPacket.PayloadPacket;

                ASCIIEncoding ascii = new ASCIIEncoding();
                Decoder decoder = ascii.GetDecoder();

                char[] chars = new char[1024];
                int bytesUsed = 0;
                int charsUsed = 0;
                bool completed = false;

                decoder.Convert(udpPacket.PayloadData, 0, udpPacket.PayloadData.Length,
                    chars, 0, 1024, true, out bytesUsed, out charsUsed, out completed);

                for (int i = 0; i < 13; i++)
                    chars[i] = (char)0;

                List<char> charList = new List<char>(chars.Where(c => !exclusionChars.Contains(c)));
                List<string> queryParts = new List<string>() { String.Empty };
                while (charList.Count > 0)
                {
                    if (char.IsControl(charList[0]))
                    {
                        charList.RemoveAt(0);
                        if (charList.Count > 0 && !char.IsControl(charList[0]))
                            queryParts.Add(String.Empty);
                    }
                    else
                    {
                        queryParts[queryParts.Count - 1] += charList[0];
                        charList.RemoveAt(0);
                    }
                }

                DatabaseInterface.InsertQuery(
                    ipPacket.SourceAddress,
                    String.Join(".", queryParts),
                    e.Packet.Timeval.Date);
            }
            catch { }
        }
    }
}
